package cn.yzj.http;

import cn.yzj.common.HttpMethod;
import cn.yzj.utils.DataUtils;
import cn.yzj.utils.HttpUtils;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.springframework.util.CollectionUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 请求工具类
 *
 * <ul>
 *   <li>配置响应时采用 {@link YzjSendConfig} 对所需参数进行配置，其对应的返回值会添加至 {@link YzjHttpResult} 当中
 *   <li>当 {@link #redirect} 为 {@code Boolean.TRUE} 时，会发送二次请求将 result 是重定向请求结果。但重定向地址会保存至 {@link
 *       YzjHttpResult#getRedirectUrl()} 当中
 *   <li>默认采取 httpclient 线程池维持请求的可持续使用
 *   <li>对于请求方式需要从 {@link HttpMethod} 中获取。TODO: 其中 PUT\HEAD\DELETE 还未进行调试
 * </ul>
 *
 * @author gzkemays
 * @since 2022/1/14 20:19
 */
@Data
public class YzjHttp {
  String url;
  String method;
  boolean redirect;
  boolean cookie;
  int socketTimeout;
  int connectTimeout;
  int connectRequestTimeout;
  Map<String, String> headers;
  YzjHttpHeaders yzjHeaders;
  Charset charset;
  Map<String, Object> dataMap;
  Object dataObj;

  public static RequestConfig getRequestDefaultConfig() {
    return RequestConfig.custom()
        .setSocketTimeout(20000)
        .setConnectTimeout(20000)
        .setConnectionRequestTimeout(20000)
        .build();
  }

  public static YzjHttpBuilder create() {
    return new YzjHttpBuilder();
  }

  public static YzjHttpBuilder create(String url, String method) {
    return new YzjHttpBuilder(url, method);
  }

  public static YzjHttpBuilder createGet(String url) {
    return new YzjHttpBuilder(url, HttpMethod.GET);
  }

  public static YzjHttpBuilder createPost(String url) {
    return new YzjHttpBuilder(url, HttpMethod.POST);
  }

  /** 基本请求配置 */
  public static final class YzjHttpBuilder {
    String url;
    String method;
    String cookies;
    boolean redirect;
    boolean cookie;
    boolean document;
    boolean json;

    boolean postJson;
    boolean postForm;
    int socketTimeout = 20000;
    int connectTimeout = 20000;
    int connectRequestTimeout = 20000;
    Map<String, Object> dataMap = new HashMap<>();
    Object dataObj;
    Charset charset = StandardCharsets.UTF_8;
    YzjHttpHeaders yzjHeaders;
    Map<String, String> headers = new HashMap<>();
    CookieStore cookieStore = new BasicCookieStore();
    CloseableHttpClient httpClient =
        HttpClients.custom()
            .setConnectionManager(HttpUtils.poolingHttpClient())
            .setDefaultCookieStore(cookieStore)
            .build();

    public YzjHttpBuilder() {}

    public YzjHttpBuilder(String url, String method) {
      this.url = url;
      this.method = method;
    }

    public YzjHttpBuilder url(String url) {
      this.url = url;
      return this;
    }

    public YzjHttpBuilder method(String method) {
      if (!HttpMethod.METHODS.contains(method.toUpperCase())) {
        throw new IllegalArgumentException("不存在对应请求，请查阅 cn.yzj.common.HttpMethod 中的参数。");
      }
      this.method = method;
      return this;
    }

    public YzjHttpBuilder config(YzjSendConfig config) {
      this.redirect = config.redirect;
      this.document = config.document;
      this.json = config.json;
      this.cookie = config.cookie;
      this.postForm = config.postForm;
      this.postJson = config.postJson;
      return this;
    }

    public YzjHttpBuilder cookies(String cookies) {
      this.cookies = cookies;
      return this;
    }

    public YzjHttpBuilder headers(Map<String, String> headers) {
      this.headers = headers;
      return this;
    }

    public YzjHttpBuilder socketTimeout(int socketTimeout) {
      this.socketTimeout = socketTimeout;
      return this;
    }

    public YzjHttpBuilder connectTimeout(int connectTimeout) {
      this.connectTimeout = connectTimeout;
      return this;
    }

    public YzjHttpBuilder connectRequestTimeout(int connectRequestTimeout) {
      this.connectRequestTimeout = connectRequestTimeout;
      return this;
    }

    public YzjHttpBuilder dataMap(Map<String, Object> dataMap) {
      this.dataMap = dataMap;
      return this;
    }

    public YzjHttpBuilder dataObj(Object dataObj) {
      this.dataObj = dataObj;
      return this;
    }

    public YzjHttpBuilder charset(Charset charset) {
      this.charset = charset;
      return this;
    }

    public YzjHttpBuilder defaultUserAgent() {
      this.headers.put(
          HTTP.USER_AGENT,
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.62");
      return this;
    }

    public YzjHttpBuilder yzjHeaders(YzjHttpHeaders yzjHeaders) {
      this.yzjHeaders = yzjHeaders;
      return this;
    }

    public YzjHttpResult send() {
      YzjHttpResult yhr = new YzjHttpResult();
      YzjHttp yzjHttp = new YzjHttp();
      if (url == null || method == null) {
        throw new NullPointerException("请确保 url 和 method 不为 null");
      }
      yzjHttp.setUrl(url);
      yzjHttp.setMethod(method);
      yzjHttp.setRedirect(redirect);
      yzjHttp.setCookie(cookie);
      yzjHttp.setSocketTimeout(socketTimeout);
      yzjHttp.setConnectTimeout(connectTimeout);
      yzjHttp.setConnectRequestTimeout(connectRequestTimeout);
      yzjHttp.setDataMap(dataMap);
      yzjHttp.setDataObj(dataObj);

      String result = execute();
      assert result != null;
      // FIXME: 连续请求，可能需要换个方式截取 redirect。
      if (redirect) {
        yhr.setRedirectUrl(result);
        url = result;
        redirect = false;
        yhr.setResult(execute());
        redirect = true;
      } else {
        if (document) {
          yhr.setDocument(Jsoup.parse(result));
        }
        if (json) {
          yhr.setJson(JSONObject.parseObject(result));
        }
        yhr.setResult(result);
      }
      if (cookie) {
        yhr.setCookie(HttpUtils.getCookie(cookieStore));
      }
      return yhr;
    }

    /** 获取请求配置 */
    private RequestConfig getRequestConfig() {
      return RequestConfig.custom()
          .setSocketTimeout(socketTimeout)
          .setConnectionRequestTimeout(connectRequestTimeout)
          .setConnectTimeout(connectTimeout)
          .setRedirectsEnabled(!redirect)
          .build();
    }

    /** 根据配置构造对应的 http post 请求，默认返回 {@link HttpPost} */
    private HttpPost getPost() {
      if (postJson) {
        if (dataObj instanceof String) {
          return HttpUtils.getPostJson(url, String.valueOf(dataObj));
        } else if (dataObj != null) {
          return HttpUtils.getPostJson(url, JSONObject.toJSONString(dataObj));
        }
        if (!dataMap.isEmpty()) {
          return HttpUtils.getPostJson(url, JSONObject.toJSONString(dataMap));
        }
        throw new NullPointerException("传参为 null，请确保配置了 dataMap 或 dataObj ");
      }
      if (postForm) {
        if (!dataMap.isEmpty()) {
          return HttpUtils.getUrlEncodedFormPost(url, dataMap, charset);
        } else if (dataObj != null) {
          return HttpUtils.getUrlEncodedFormPost(url, DataUtils.getMapFromObject(dataObj), charset);
        }
        throw new NullPointerException("传参为 null，请确保配置了 dataMap 或 dataObj ");
      }
      return HttpUtils.getHttpPost(url);
    }
    /**
     * 执行发送请求
     *
     * @return 请求结果
     */
    // TODO：未编写 PUT\DELETE\HEAD 的请求
    private String execute() {
      CloseableHttpResponse response;
      try {
        switch (method) {
          case HttpMethod.GET:
            // 发送 GET 请求
            HttpGet get =
                dataMap.isEmpty() ? HttpUtils.getHttpGet(url) : HttpUtils.getDataGet(url, dataMap);
            get.setConfig(getRequestConfig());
            setHttpGetHeaders(get, headers);
            response = httpClient.execute(get);
            if (redirect) {
              return response.getFirstHeader("location").getValue();
            }
            return EntityUtils.toString(response.getEntity(), charset);
          case HttpMethod.POST:
            // 发送 POST 请求
            HttpPost post = getPost();
            post.setConfig(getRequestConfig());
            setHttpPostHeaders(post, headers);
            response = httpClient.execute(post);
            if (redirect) {
              return response.getFirstHeader("location").getValue();
            }
            return EntityUtils.toString(response.getEntity(), charset);
          case HttpMethod.PUT:
            // 发送 PUT 请求
            break;
          case HttpMethod.DELETE:
            // 发送 DELETE 请求
            break;
          case HttpMethod.HEAD:
            // 发送 HEAD 请求
            break;
          default:
            throw new IllegalArgumentException("无指定参数");
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
      return null;
    }

    /** 忽略 SSL 证书 */
    private SSLContext getIgnoreSslContext() {
      try {
        return SSLContexts.custom()
            .loadTrustMaterial(
                null,
                new TrustStrategy() {
                  @Override
                  public boolean isTrusted(X509Certificate[] x509Certificates, String s) {
                    return true;
                  }
                })
            .build();
      } catch (Exception e) {
        e.printStackTrace();
      }
      return null;
    }

    private void setHttpGetHeaders(HttpGet get, Map<String, String> headers) {
      if (StringUtils.isNotBlank(cookies)) {
        get.setHeader("Cookie", cookies);
      }
      if (yzjHeaders != null) {
        String key = yzjHeaders.getKey();
        String value = yzjHeaders.getValue();
        if (Objects.nonNull(key) && Objects.nonNull(value)) {
          get.setHeader(key, value);
        }
        List<String> keys = yzjHeaders.getKeys();
        List<String> values = yzjHeaders.getValues();
        if (!CollectionUtils.isEmpty(keys) && !CollectionUtils.isEmpty(values)) {
          for (int i = 0; i < keys.size(); i++) {
            get.setHeader(keys.get(i), values.get(i));
          }
        }
      }

      if (!headers.isEmpty()) {
        for (Map.Entry<String, String> entry : headers.entrySet()) {
          // 循环加入
          get.setHeader(entry.getKey(), entry.getValue());
        }
      }
    }

    private void setHttpPostHeaders(HttpPost post, Map<String, String> headers) {
      if (StringUtils.isNotBlank(cookies)) {
        post.setHeader("Cookie", cookies);
      }
      if (yzjHeaders != null) {
        String key = yzjHeaders.getKey();
        String value = yzjHeaders.getValue();
        if (Objects.nonNull(key) && Objects.nonNull(value)) {
          post.setHeader(key, value);
        }
        List<String> keys = yzjHeaders.getKeys();
        List<String> values = yzjHeaders.getValues();
        if (!CollectionUtils.isEmpty(keys) && !CollectionUtils.isEmpty(values)) {
          for (int i = 0; i < keys.size(); i++) {
            post.setHeader(keys.get(i), values.get(i));
          }
        }
      }
      if (!headers.isEmpty()) {
        for (Map.Entry<String, String> entry : headers.entrySet()) {
          // 循环加入
          post.setHeader(entry.getKey(), entry.getValue());
        }
      }
    }
  }
}
